package fons.navigator.views.island.maps;

import java.awt.Color;
import java.awt.geom.Ellipse2D;
import java.util.Collections;
import java.util.List;

import com.google.common.collect.Lists;

import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.nodes.PPath;
import fons.model.ontology.ValueSpace;
import fons.navigator.util.piccolo.PButton;
import fons.navigator.views.island.ColorField;
import fons.navigator.views.island.Island;
import fons.navigator.views.island.MapColorManager;
import fons.navigator.views.island.MapModel;
import fons.navigator.views.island.VisualIsland;

public class KMeansMapModel extends MapModel {
	private final static int DEFAULT_CLUSTER_SIZE = 5;
	private int k;
	private List<Cluster> clusters = null;

	private ForceColorField colorField;

	private class Cluster extends PPath {
		private static final long serialVersionUID = 1L;
		private double[] means;
		private List<Island> islands = Lists.newArrayList();

		public Cluster (int dimensionality) {
			super(new Ellipse2D.Double(-8, -8, 16, 16));
			this.setPaint(Color.RED);
			this.means = new double[dimensionality];
			for (int i = 0; i < dimensionality; ++i) {
				this.means[i] = Math.random();
			}
		}

		public void recalculateMeans() {
			int d = 0;
			for (ValueSpace vsp: getVisualIsland().getValueSpaces()) {
				double tmp = 0;
				for (Island island: islands) {
					tmp += island.getValueOf(vsp);
				}
				final double mean = tmp/islands.size();
				this.means[d] = mean;
				d++;
			}
		}

		public void reset() {
			this.islands.clear();
		}

		public double distanceTo(Island island) {
			int d = 0;
			double distance = 0;
			for (ValueSpace vsp: getVisualIsland().getValueSpaces()) {
				final double value = island.getValueOf(vsp);
				distance += (means[d] - value) * (means[d] - value);
				d++;
			}
			return Math.sqrt(distance);
		}

		public void add(Island island) {
			this.islands.add(island);
		}

		public List<Island> getIslands() {
			return Collections.unmodifiableList(this.islands);
		}

		public double getMean(int i) {
			return means[i];
		}
	}

	public KMeansMapModel(VisualIsland island) {
		super(island);

		this.newClusters(DEFAULT_CLUSTER_SIZE);
	}

	private void newClusters(int k) {
		if (this.clusters != null) {
			for (Cluster cluster: clusters) {
				getVisualIsland().removeChild(cluster);
			}
		}

		this.k = k;
		this.clusters = Lists.newArrayListWithCapacity(k);

		for (int i = 0; i < k; ++i) {
			final Cluster cluster = new Cluster(getVisualIsland().getDimensionality());
			getVisualIsland().addChild(cluster);
			clusters.add(cluster);
		}
	}

	@Override
	public void step(double time) {
		resetClusters();
		for (Island island: getVisualIsland().getIslands()) {
			final Cluster bestCluster = getBestClusterFor(island);
			bestCluster.add(island);
		}
		recalculateMeans();
	}

	private void drawClusters() {
		final double RADIUS = 40 * clusters.size();
		final double ANGLE = Math.PI*2/clusters.size();

		int i = 0;
		for (Cluster cluster: clusters) {
			final double x = RADIUS* Math.cos(ANGLE * i);
			final double y = RADIUS* Math.sin(ANGLE * i);

			cluster.setOffset(x, y);

			final double islandAngle = Math.PI*2/cluster.getIslands().size() + Math.PI/64.0;
			final double islandRadius = 10 * clusters.size();
			int j = 0;
			for (Island island: cluster.getIslands()) {
				final double distance = cluster.distanceTo(island);
				final double ix = (distance * islandRadius + 10) * Math.cos(islandAngle * j);
				final double iy = (distance * islandRadius + 10) * Math.sin(islandAngle * j);
				island.setOffset(x + ix, y + iy);
				j++;
			}

			++i;
		}
	}

	private void recalculateMeans() {
		for (Cluster cluster: clusters) {
			cluster.recalculateMeans();
		}
	}

	private Cluster getBestClusterFor(Island island) {
		Cluster best = null;
		double bestDistance = Double.NaN;
		for (Cluster cluster: clusters) {
			final double distance = cluster.distanceTo(island);
			if (best == null || distance < bestDistance) {
				best = cluster;
				bestDistance = distance;
			}
		}
		return best;
	}

	private void resetClusters() {
		for (Cluster cluster: clusters) {
			cluster.reset();
		}
	}

	@Override
	public ColorField getColorField() {
		if (colorField == null) updateColorField();
		return colorField;
	}

	public void bigStep() {
		for (int i = 0; i < 50; ++i) 
			step(0);

		drawClusters();
		updateColorField();
	}

	@Override
	public void createButtons() {
		getButtonsPanel().addButton(new PButton(" Reset "), new PBasicInputEventHandler() {
			public void mouseClicked(PInputEvent event) {
				newClusters(k);
				bigStep();
			}
		});
		
		getButtonsPanel().addButton(new PButton(" Step "), new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				bigStep();
			}
		});	

		getButtonsPanel().addButton(new PButton(" Mehr Cluster "), new PBasicInputEventHandler() {
			public void mouseClicked(PInputEvent event) {
				newClusters(k+1);
				bigStep();
			}
		});

		getButtonsPanel().addButton(new PButton(" Weniger Cluster "), new PBasicInputEventHandler() {
			@Override
			public void mouseClicked(PInputEvent event) {
				newClusters(k-1 < 2 ? 2 : k-1);
				bigStep();
			}
		});
	}

	@Override
	public void updateColorField() {
		colorField = new ForceColorField(getVisualIsland());
		final MapColorManager colors = getVisualIsland().getColorManager();

		for (Cluster cluster: clusters) {
			float r = 0, g = 0, b = 0;
			int i = 0;
			for (ValueSpace vsp: getVisualIsland().getValueSpaces()) {
					r += colors.redOf(vsp, (float) cluster.getMean(i));
					g += colors.greenOf(vsp, (float) cluster.getMean(i));
					b += colors.blueOf(vsp, (float) cluster.getMean(i));
					i++;
			}			
			cluster.setPaint(new Color(r > 1 ? 1 : r, g > 1 ? 1 : g, b > 1 ? 1 : b));
		}
		getVisualIsland().getMapGrid().updateColors();
	}
}
